/*
 ------------------------------------------------------------------------------
 Copyright (C) 1996-2001 Id Software, Inc.

 This file is part of the Quake source code.

 The Quake source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Quake source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Quake source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// plane.h - plane math library
//


#ifndef __MATH_PLANE_H__
#define __MATH_PLANE_H__


#define ON_EPSILON					0.1f

#define NORMAL_EPSILON				0.00001f
#define DIST_EPSILON				0.01f

// Plane sides
#define	PLANESIDE_FRONT				1
#define	PLANESIDE_BACK				2
#define PLANESIDE_CROSS				3
#define	PLANESIDE_ON				3

extern glqPlane		plane_origin;

class glqPlane {
private:
	float			a;
	float			b;
	float			c;
	float			d;

	int				signBits;

public:
					glqPlane (void);
	explicit		glqPlane (const glqVec3 &normal, const float dist);
	explicit		glqPlane (const float a, const float b, const float c, const float d);

	float			operator [] (const int index) const;
	float &			operator [] (const int index);

	glqPlane &		operator = (const glqPlane &plane);
	bool			operator == (const glqPlane &plane) const;
	bool			operator != (const glqPlane &plane) const;

	glqPlane			operator - () const;

	bool			Compare (const glqPlane &plane) const;
	bool			Compare (const glqPlane &plane, const float epsilon) const;
	bool			Compare (const glqPlane &plane, const float normalEpsilon, const float distEpsilon) const;
	void			Set (const glqVec3 &normal, const float dist);
	void			Set (const float a, const float b, const float c, const float d);
	bool			SetFromPoints (const glqVec3 &p1, const glqVec3 &p2, const glqVec3 &p3);
	void			SetFromPointsFast (const glqVec3 &p1, const glqVec3 &p2, const glqVec3 &p3);
	bool			SetFromVectors (const glqVec3 &dir1, const glqVec3 &dir2, const glqVec3 &point);
	void			SetFromVectorsFast (const glqVec3 &dir1, const glqVec3 &dir2, const glqVec3 &point);
	void			SetNormal (const glqVec3 &normal);
	void			SetDist (const float dist);
	void			SetSignBits (void);
	void			Snap (const float normalEpsilon = NORMAL_EPSILON, const float distEpsilon = DIST_EPSILON);
	void			SnapNormal (const float epsilon = NORMAL_EPSILON);
	void			SnapDist (const float epsilon = DIST_EPSILON);
	float			Normalize (void);
	void			NormalizeFast (void);
	float			Distance (const glqVec3 &point) const;
	int				BoundsOnSide (const glqBounds &bounds, const float epsilon = 0.0f) const;
	int				SphereOnSide (const glqSphere &sphere, const float epsilon = 0.0f) const;
	int				LineOnSide (const glqVec3 &start, const glqVec3 &end, const float epsilon = 0.0f) const;
	int				PointOnSide (const glqVec3 &point, const float epsilon = 0.0f) const;

	const glqVec3 &	Normal (void) const;
	glqVec3 &		Normal (void);
	float			Dist (void) const;
	int				SignBits (void) const;

	glqVec4			ToVec4 (void) const;
	void			FromVec4 (const glqVec4 &vec);

	const float *	ToFloatPtr (void) const;
	float *			ToFloatPtr (void);

	const char *	ToString (int precision = 2) const;
};

INLINE glqPlane::glqPlane (void){

}

INLINE glqPlane::glqPlane (const glqVec3 &normal, const float dist){

	this->a = normal.x;
	this->b = normal.y;
	this->c = normal.z;
	this->d = -dist;

	this->signBits = (FLOATSIGNBITSET(normal.x) << 0) | (FLOATSIGNBITSET(normal.y) << 1) | (FLOATSIGNBITSET(normal.z) << 2);
}

INLINE glqPlane::glqPlane (const float a, const float b, const float c, const float d){

	this->a = a;
	this->b = b;
	this->c = c;
	this->d = d;

	this->signBits = (FLOATSIGNBITSET(a) << 0) | (FLOATSIGNBITSET(b) << 1) | (FLOATSIGNBITSET(c) << 2);
}

INLINE float glqPlane::operator [] (const int index) const {

	return (&a)[index];
}

INLINE float &glqPlane::operator [] (const int index){

	return (&a)[index];
}

INLINE glqPlane &glqPlane::operator = (const glqPlane &plane){

	a = plane.a;
	b = plane.b;
	c = plane.c;
	d = plane.d;

	signBits = plane.signBits;

	return *this;
}

INLINE bool glqPlane::operator == (const glqPlane &plane) const {

	if (a != plane.a || b != plane.b || c != plane.c || d != plane.d)
		return false;

	return true;
}

INLINE bool glqPlane::operator != (const glqPlane &plane) const {

	if (a == plane.a && b == plane.b && c == plane.c && d == plane.d)
		return false;

	return true;
}

INLINE glqPlane glqPlane::operator - () const {

	return glqPlane(-a, -b, -c, -d);
}

INLINE bool glqPlane::Compare (const glqPlane &plane) const {

	if (a != plane.a)
		return false;
	if (b != plane.b)
		return false;
	if (c != plane.c)
		return false;
	if (d != plane.d)
		return false;

	return true;
}

INLINE bool glqPlane::Compare (const glqPlane &plane, const float epsilon) const {

	if (glqMath::FAbs(a - plane.a) > epsilon)
		return false;
	if (glqMath::FAbs(b - plane.b) > epsilon)
		return false;
	if (glqMath::FAbs(c - plane.c) > epsilon)
		return false;
	if (glqMath::FAbs(d - plane.d) > epsilon)
		return false;

	return true;
}

INLINE bool glqPlane::Compare (const glqPlane &plane, const float normalEpsilon, const float distEpsilon) const {

	if (glqMath::FAbs(a - plane.a) > normalEpsilon)
		return false;
	if (glqMath::FAbs(b - plane.b) > normalEpsilon)
		return false;
	if (glqMath::FAbs(c - plane.c) > normalEpsilon)
		return false;
	if (glqMath::FAbs(d - plane.d) > distEpsilon)
		return false;

	return true;
}

INLINE void glqPlane::Set (const glqVec3 &normal, const float dist){

	this->a = normal.x;
	this->b = normal.y;
	this->c = normal.z;
	this->d = -dist;

	this->signBits = (FLOATSIGNBITSET(normal.x) << 0) | (FLOATSIGNBITSET(normal.y) << 1) | (FLOATSIGNBITSET(normal.z) << 2);
}

INLINE void glqPlane::Set (const float a, const float b, const float c, const float d){

	this->a = a;
	this->b = b;
	this->c = c;
	this->d = d;

	this->signBits = (FLOATSIGNBITSET(a) << 0) | (FLOATSIGNBITSET(b) << 1) | (FLOATSIGNBITSET(c) << 2);
}

INLINE bool glqPlane::SetFromPoints (const glqVec3 &p1, const glqVec3 &p2, const glqVec3 &p3){

	Normal().Cross(p3 - p1, p2 - p1);

	if (!Normal().Normalize()){
		d = 0.0f;
		return false;
	}

	d = -Normal().Dot(p1);

	signBits = (FLOATSIGNBITSET(a) << 0) | (FLOATSIGNBITSET(b) << 1) | (FLOATSIGNBITSET(c) << 2);

	return true;
}

INLINE void glqPlane::SetFromPointsFast (const glqVec3 &p1, const glqVec3 &p2, const glqVec3 &p3){

	Normal().Cross(p3 - p1, p2 - p1);
	Normal().NormalizeFast();

	d = -Normal().Dot(p1);

	signBits = (FLOATSIGNBITSET(a) << 0) | (FLOATSIGNBITSET(b) << 1) | (FLOATSIGNBITSET(c) << 2);
}

INLINE bool glqPlane::SetFromVectors (const glqVec3 &dir1, const glqVec3 &dir2, const glqVec3 &point){

	Normal().Cross(dir1, dir2);

	if (!Normal().Normalize()){
		d = 0.0f;
		return false;
	}

	d = -Normal().Dot(point);

	signBits = (FLOATSIGNBITSET(a) << 0) | (FLOATSIGNBITSET(b) << 1) | (FLOATSIGNBITSET(c) << 2);

	return true;
}

INLINE void glqPlane::SetFromVectorsFast (const glqVec3 &dir1, const glqVec3 &dir2, const glqVec3 &point){

	Normal().Cross(dir1, dir2);
	Normal().NormalizeFast();

	d = -Normal().Dot(point);

	signBits = (FLOATSIGNBITSET(a) << 0) | (FLOATSIGNBITSET(b) << 1) | (FLOATSIGNBITSET(c) << 2);
}

INLINE void glqPlane::SetNormal (const glqVec3 &normal){

	a = normal.x;
	b = normal.y;
	c = normal.z;
}

INLINE void glqPlane::SetDist (const float dist){

	d = -dist;
}

INLINE void glqPlane::SetSignBits (void){

	signBits = (FLOATSIGNBITSET(a) << 0) | (FLOATSIGNBITSET(b) << 1) | (FLOATSIGNBITSET(c) << 2);
}

INLINE void glqPlane::Snap (const float normalEpsilon, const float distEpsilon){

	int		i;

	for (i = 0; i < 3; i++){
		if (glqMath::FAbs(Normal()[i] - 1.0f) < normalEpsilon){
			Normal().Clear();
			Normal()[i] = 1.0f;

			break;
		}

		if (glqMath::FAbs(Normal()[i] + 1.0f) < normalEpsilon){
			Normal().Clear();
			Normal()[i] = -1.0f;

			break;
		}
	}

	if (glqMath::FAbs(d - glqMath::Round(d)) < distEpsilon)
		d = glqMath::Round(d);
}

INLINE void glqPlane::SnapNormal (const float epsilon){

	int		i;

	for (i = 0; i < 3; i++){
		if (glqMath::FAbs(Normal()[i] - 1.0f) < epsilon){
			Normal().Clear();
			Normal()[i] = 1.0f;

			break;
		}

		if (glqMath::FAbs(Normal()[i] + 1.0f) < epsilon){
			Normal().Clear();
			Normal()[i] = -1.0f;

			break;
		}
	}
}

INLINE void glqPlane::SnapDist (const float epsilon){

	if (glqMath::FAbs(d - glqMath::Round(d)) < epsilon)
		d = glqMath::Round(d);
}

INLINE float glqPlane::Normalize (void){

	float	length, invLength;

	length = glqMath::Sqrt(a*a + b*b + c*c);

	if (length){
		invLength = 1.0f / length;

		a *= invLength;
		b *= invLength;
		c *= invLength;
		d *= invLength;
	}

	return length;
}

INLINE void glqPlane::NormalizeFast (void){

	float	invLength;

	invLength = glqMath::RSqrt(a*a + b*b + c*c);

	a *= invLength;
	b *= invLength;
	c *= invLength;
	d *= invLength;
}

INLINE float glqPlane::Distance (const glqVec3 &point) const {

	return a * point.x + b * point.y + c * point.z + d;
}

INLINE int glqPlane::BoundsOnSide (const glqBounds &bounds, const float epsilon) const {

	float	dist1, dist2;

	switch (signBits){
	case 0:
		dist1 = a * bounds[1][0] + b * bounds[1][1] + c * bounds[1][2] + d;
		dist2 = a * bounds[0][0] + b * bounds[0][1] + c * bounds[0][2] + d;
		break;
	case 1:
		dist1 = a * bounds[0][0] + b * bounds[1][1] + c * bounds[1][2] + d;
		dist2 = a * bounds[1][0] + b * bounds[0][1] + c * bounds[0][2] + d;
		break;
	case 2:
		dist1 = a * bounds[1][0] + b * bounds[0][1] + c * bounds[1][2] + d;
		dist2 = a * bounds[0][0] + b * bounds[1][1] + c * bounds[0][2] + d;
		break;
	case 3:
		dist1 = a * bounds[0][0] + b * bounds[0][1] + c * bounds[1][2] + d;
		dist2 = a * bounds[1][0] + b * bounds[1][1] + c * bounds[0][2] + d;
		break;
	case 4:
		dist1 = a * bounds[1][0] + b * bounds[1][1] + c * bounds[0][2] + d;
		dist2 = a * bounds[0][0] + b * bounds[0][1] + c * bounds[1][2] + d;
		break;
	case 5:
		dist1 = a * bounds[0][0] + b * bounds[1][1] + c * bounds[0][2] + d;
		dist2 = a * bounds[1][0] + b * bounds[0][1] + c * bounds[1][2] + d;
		break;
	case 6:
		dist1 = a * bounds[1][0] + b * bounds[0][1] + c * bounds[0][2] + d;
		dist2 = a * bounds[0][0] + b * bounds[1][1] + c * bounds[1][2] + d;
		break;
	case 7:
		dist1 = a * bounds[0][0] + b * bounds[0][1] + c * bounds[0][2] + d;
		dist2 = a * bounds[1][0] + b * bounds[1][1] + c * bounds[1][2] + d;
		break;
	default:
		dist1 = 0.0f;
		dist2 = 0.0f;
		break;
	}

	if (dist1 > epsilon && dist2 > epsilon)
		return PLANESIDE_FRONT;
	if (dist1 < -epsilon && dist2 < -epsilon)
		return PLANESIDE_BACK;

	return PLANESIDE_CROSS;
}

INLINE int glqPlane::SphereOnSide (const glqSphere &sphere, const float epsilon) const {

	float	dist;

	dist = a * sphere[0] + b * sphere[1] + c * sphere[2] + d;

	if (dist > sphere[3] + epsilon)
		return PLANESIDE_FRONT;
	if (dist < -sphere[3] - epsilon)
		return PLANESIDE_BACK;

	return PLANESIDE_CROSS;
}

INLINE int glqPlane::LineOnSide (const glqVec3 &start, const glqVec3 &end, const float epsilon) const {

	float	dist1, dist2;

	dist1 = a * start[0] + b * start[1] + c * start[2] + d;
	dist2 = a * end[0] + b * end[1] + c * end[2] + d;

	if (dist1 > epsilon && dist2 > epsilon)
		return PLANESIDE_FRONT;
	if (dist1 < -epsilon && dist2 < -epsilon)
		return PLANESIDE_BACK;

	return PLANESIDE_CROSS;
}

INLINE int glqPlane::PointOnSide (const glqVec3 &point, const float epsilon) const {

	float	dist;

	dist = a * point[0] + b * point[1] + c * point[2] + d;

	if (dist > epsilon)
		return PLANESIDE_FRONT;
	if (dist < -epsilon)
		return PLANESIDE_BACK;

	return PLANESIDE_ON;
}

INLINE const glqVec3 &glqPlane::Normal (void) const {

	return *reinterpret_cast <const glqVec3 *> (&a);
}

INLINE glqVec3 &glqPlane::Normal (void){

	return *reinterpret_cast <glqVec3 *> (&a);
}

INLINE float glqPlane::Dist (void) const {

	return -d;
}

INLINE int glqPlane::SignBits (void) const {

	return signBits;
}

INLINE const float *glqPlane::ToFloatPtr (void) const {

	return &a;
}

INLINE float *glqPlane::ToFloatPtr (void) {

	return &a;
}


#endif	// __MATH_PLANE_H__